package com.lambkit.core;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import com.lambkit.core.annotion.App;
import com.lambkit.core.annotion.Config;
import com.lambkit.core.annotion.Order;
import com.lambkit.core.annotion.ScanPackage;
import com.lambkit.core.config.AppConfig;
import com.lambkit.core.exception.LambkitException;
import com.lambkit.core.pipeline.PipeLineContext;
import com.lambkit.core.pipeline.Valve;
import com.lambkit.core.plugin.IPlugin;
import com.lambkit.core.service.ScanClassProcess;
import com.lambkit.util.Printer;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class AppInitValve extends Valve {
    @Override
    public void invoke(PipeLineContext context, boolean isHandled) {
        String[] args = context.getAttr("args");
        Class<?> target = Lambkit.context().getTargetClass();
        if(Lambkit.app()==null) {
            throw new LambkitException("Lambkit app is null, please check your main class.");
        }
        Printer.print(this, "starter", "AppInitValve mainApp {}", Lambkit.app().getClass().getName());
        Printer.print(this, "starter", "AppInitValve targetClass {}", target.getName());
        //主配置
        Class<? extends AppConfig> mainConfig = null;
        Config annoConfig = target.getAnnotation(Config.class);
        if (annoConfig != null) {
            if (annoConfig.value() != null) {
                mainConfig = annoConfig.value();
                AppConfig appConfig = Lambkit.get(annoConfig.value());
                if(appConfig != null) {
                    appConfig.setContext(Lambkit.app().context());
                    Lambkit.app().context().addAppConfig(appConfig);
                    Printer.print(this, "starter", "LambkitApp [{}] add AppConfig: {}", Lambkit.app().getClass().getName(), mainConfig.getName());
                }
            }
        }
        //scan
        List<AppConfigInfo> configInfos = new ArrayList<>();
        List<PluginInfo> pluginInfos = new ArrayList<>();
        scanConfig(configInfos, pluginInfos, mainConfig, target, args);
        //遍历app
        Collection<LambkitApp> apps = Lambkit.context().appCollection();
        for (LambkitApp app : apps) {
            TimeInterval timer = DateUtil.timer();
            if(app == null || !app.isReady()) {
                break;
            }
            try {
                init(app, configInfos, pluginInfos, target, args);
            } catch (LambkitException | LifecycleException e) {
                throw new LambkitException(e);
            }
            Printer.print(this, "starter", "LambkitApp [{}] Init Complete in {}ms.", app.getClass().getName(), timer.interval());
        }
        next(context, isHandled);
    }

    public void scanConfig(List<AppConfigInfo> configInfos, List<PluginInfo> pluginInfos, Class<? extends AppConfig> mainConfig, Class<?> target, String[] args) {
        //搜索LambkitApp的之类
        List<String> packages = null;
        ScanPackage scanPackage = target.getAnnotation(ScanPackage.class);
        if(scanPackage != null) {
            String[] pkgs = scanPackage.value();
            if(pkgs!=null && pkgs.length > 0) {
                packages = CollUtil.newArrayList(pkgs);
            }
        }
        if(packages == null) {
            packages = CollUtil.newArrayList(ClassUtil.getPackage(target));
        }
        for (String packageName : packages) {
            Lambkit.context().classOperateService().scanPackageBySuper(packageName, AppConfig.class, new ScanClassProcess() {
                @Override
                public void process(Class<?> clazz) {
                    if(mainConfig!=null && clazz.equals(mainConfig)) {
                        return;
                    }
                    AppConfigInfo appConfigInfo = new AppConfigInfo();
                    appConfigInfo.clazz = (Class<AppConfig>) clazz;
                    appConfigInfo.appAnno = clazz.getAnnotation(App.class);
                    appConfigInfo.orderAnno = clazz.getAnnotation(Order.class);
                    configInfos.add(appConfigInfo);
                }
            });
            Lambkit.context().classOperateService().scanPackageBySuper(packageName, IPlugin.class, new ScanClassProcess() {
                @Override
                public void process(Class<?> clazz) {
                    PluginInfo pluginInfo = new PluginInfo();
                    pluginInfo.clazz = (Class<IPlugin>) clazz;
                    pluginInfo.appAnno = clazz.getAnnotation(App.class);
                    pluginInfo.orderAnno = clazz.getAnnotation(Order.class);
                    pluginInfos.add(pluginInfo);
                }
            });
        }
    }

    public void init(LambkitApp app, List<AppConfigInfo> configInfos, List<PluginInfo> pluginInfos, Class<?> target, String[] args) throws LifecycleException {
        app.init();
        List<AppConfig> configs = new ArrayList<>();
        List<IPlugin> plugins = new ArrayList<>();
        for(AppConfigInfo configInfo : configInfos) {
            Class<AppConfig> clazz = configInfo.clazz;
            App appAnno = configInfo.appAnno;
            if(appAnno != null) {
                if(!ArrayUtil.contains(appAnno.value(), app.getClass())) {
                    return;
                }
            } else if(!app.equals(Lambkit.app())) {
                return;
            }
            Order orderAnno = clazz.getAnnotation(Order.class);
            try {
                AppConfig config = (AppConfig) app.getBeanFactory().get(clazz);
                int index = configs.size();
                if(orderAnno != null) {
                    //排序
                    if(orderAnno.index() < index) {
                        index = orderAnno.index();
                    }
                }
                Printer.print(this, "starter", "LambkitApp [{}] add AppConfig: {}", app.getClass().getName(), clazz.getName());
                configs.add(index, config);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        for(PluginInfo pluginInfo : pluginInfos) {
            Class<IPlugin> clazz = pluginInfo.clazz;
            App appAnno = pluginInfo.appAnno;
            if(appAnno != null) {
                if(!ArrayUtil.contains(appAnno.value(), app.getClass())) {
                    return;
                }
            } else if(!app.equals(Lambkit.app())) {
                return;
            }
            Order orderAnno = clazz.getAnnotation(Order.class);
            try {
                IPlugin plugin = (IPlugin) app.getBeanFactory().get(clazz);
                int index = plugins.size();
                if(orderAnno != null) {
                    //排序
                    if(orderAnno.index() < index) {
                        index = orderAnno.index();
                    }
                }
                Printer.print(this, "starter", "LambkitApp [{}] add Plugin: {}", app.getClass().getName(), clazz.getName());
                plugins.add(index, plugin);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        app.context().addAppConfigs(configs);
        app.context().addPlugins(plugins);
    }

    class AppConfigInfo {
        public Class<AppConfig> clazz;
        public Order orderAnno;
        public App appAnno;
    }

    class PluginInfo {
        public Class<IPlugin> clazz;
        public Order orderAnno;
        public App appAnno;
    }
}
